package com.ilxqx.framework.security.util;

import com.ilxqx.framework.security.constant.SecurityConstants;
import com.ilxqx.framework.security.po.SimpleUserInfo;
import com.ilxqx.framework.security.po.PermissionEntry;
import com.ilxqx.framework.security.po.UserDetailsImpl;
import com.ilxqx.framework.security.strategy.MatchedPermissionEntryHolderStrategy;
import com.ilxqx.framework.security.strategy.ThreadLocalMatchedPermissionEntryHolderStrategy;
import com.ilxqx.framework.system.enumeration.*;
import com.ilxqx.framework.util.DbUtils;
import com.ilxqx.framework.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 权限工具类
 * @author venus
 */
@Slf4j
public class AuthUtils {

    /**
     * 权限在Redis中存活的最大时间
     */
    private static final Duration PERMISSION_LIVE_TIME_IN_REDIS = Duration.ofHours(6);

    /**
     * 认证对象判定器
     */
    public static AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    /**
     * 匹配的权限项保存策略对象
     */
    private static MatchedPermissionEntryHolderStrategy matchedPermissionEntryHolderStrategy = new ThreadLocalMatchedPermissionEntryHolderStrategy();

    /**
     * Ant风格的路径匹配对象
     */
    private static AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 权限保存的命名前缀
     */
    private static final String NAMESPACE = "permission:";

    private static Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * 判断当前是否是登录的状态
     * @return bool
     */
    private static boolean isLogged() {
        Authentication authentication = AuthUtils.getAuthentication();
        return authentication != null && !AuthUtils.authenticationTrustResolver.isAnonymous(authentication);
    }

    /**
     * 获取用户信息
     * @return 用户信息
     */
    private static UserDetailsImpl getUserDetails() {
        Authentication authentication = AuthUtils.getAuthentication();
        if (isLogged()) {
            return (UserDetailsImpl) authentication.getPrincipal();
        }
        return null;
    }

    /**
     * 获取当前登录用户的用户ID，如果没有，则使用超级管理员的ID
     * @return 用户ID
     */
    @NotNull
    public static Long getLoggedUserId() {
        UserDetailsImpl userDetails = AuthUtils.getUserDetails();
        if (userDetails == null) {
            // 没有登录用户，使用超级管理员的ID
            return SecurityConstants.SUPER_ADMIN_USER_ID;
        }
        return userDetails.getUserId();
    }

    /**
     * 获取当前登录用户的用户姓名，如果没有，则使用超级管理员的姓名
     * @return 用户姓名
     */
    @NotNull
    public static String getLoggedUserName() {
        UserDetailsImpl userDetails = AuthUtils.getUserDetails();
        if (userDetails == null) {
            // 没有登录用户，使用超级管理员的姓名
            return SecurityConstants.SUPER_ADMIN_USER_NAME;
        }
        return userDetails.getName();
    }

    /**
     * 获取当前登录用户的姓名和ID，如果没有则使用超级管理员的相关信息
     * @return 登录用户信息
     */
    @NotNull
    public static SimpleUserInfo getLoggedUserSimpleInfo() {
        UserDetailsImpl userDetails = AuthUtils.getUserDetails();
        if (userDetails == null) {
            // 没有登录用户，使用超级管理员的ID
            return new SimpleUserInfo(SecurityConstants.SUPER_ADMIN_USER_ID, SecurityConstants.SUPER_ADMIN_USER_NAME);
        }
        return new SimpleUserInfo(userDetails.getUserId(), userDetails.getName());
    }

    /**
     * 刷新登录用户的权限
     */
    public static void refreshLoggedUserPermissions() {
        AuthUtils.refreshPermissions(AuthUtils.getLoggedUserId());
    }

    /**
     * 判断当前登录用户是否对一次请求有权限
     * @param uri 请求URI
     * @param method 请求方法
     * @return bool
     */
    public static boolean loggedUserHasPermission(String uri, String method) {
        return AuthUtils.hasPermission(uri, method, AuthUtils.getLoggedUserId());
    }

    /**
     * 判断用户是否对一次请求有权限
     * @param uri 请求URI
     * @param method 请求方法
     * @param userId 用户ID
     * @return 是否有权限
     */
    public static boolean hasPermission(String uri, String method, Long userId) {
        // 如果是超级管理员，不用判断，肯定有权限
        if (Objects.equals(userId, SecurityConstants.SUPER_ADMIN_USER_ID)) {
            return true;
        }
        String key = AuthUtils.NAMESPACE + String.valueOf(userId);
        List<PermissionEntry> permissionEntries =  RedisUtils.get(key);
        if (permissionEntries == null) {
            permissionEntries = refreshPermissions(userId);
        }
        return permissionEntries.stream()
                .anyMatch(permissionEntry -> AuthUtils.uriIsMatch(uri, method, permissionEntry));
    }

    /**
     * 判断URI是否匹配
     * @param uri 请求URI
     * @param method 请求方法
     * @param permissionEntry 权限项
     * @return bool
     */
    public static boolean uriIsMatch(String uri, String method, PermissionEntry permissionEntry) {
        boolean matched = permissionEntry.getUris().stream().anyMatch(entryUri -> AuthUtils.antPathMatcher.match(entryUri, uri)) && (
                permissionEntry.getMethod().equals(SysPermissionMethodEnum.ALL) ||
                        StringUtils.equalsIgnoreCase(permissionEntry.getMethod().getMeaning(), method)
        );
        if (matched) {
            // 匹配
            AuthUtils.matchedPermissionEntryHolderStrategy.setPermissionEntry(permissionEntry);
            return true;
        }
        return false;
    }

    /**
     * 刷新用户权限
     * @param userId 用户ID
     * @return 用户权限项List
     */
    private static List<PermissionEntry> refreshPermissions(Long userId) {
        String key = AuthUtils.NAMESPACE + String.valueOf(userId);
        List<PermissionEntry> permissionEntries;// 没有权限，或者过期了，重新获取
        permissionEntries = AuthUtils.getUserPermissions(userId);
        // 设置在redis里面
        RedisUtils.set(key, permissionEntries, PERMISSION_LIVE_TIME_IN_REDIS);
        return permissionEntries;
    }

    /**
     * 移除当前登录用户的权限信息
     */
    public static void removeLoggedUserPermissions() {
        AuthUtils.removePermissions(AuthUtils.getLoggedUserId());
    }

    /**
     * 移除用户的权限信息
     * @param userId 用户ID
     */
    private static void removePermissions(Long userId) {
        String key = AuthUtils.NAMESPACE + String.valueOf(userId);
        RedisUtils.remove(key);
    }

    /**
     * 获取用户的权限项
     * @param userId 用户ID
     * @return 权限项List
     */
    private static List<PermissionEntry> getUserPermissions(Long userId) {
        return DbUtils.query(
                "SELECT sp.uri, sp.method, sr.data_operation_range FROM `sys_permission` AS sp\n" +
                "INNER JOIN `sys_role_permission` AS srp\n" +
                "ON srp.permission_id = sp.permission_id\n" +
                "INNER JOIN `sys_role` AS sr\n" +
                "ON sr.role_id = srp.role_id\n" +
                "INNER JOIN `sys_user_role` AS sur\n" +
                "ON sur.role_id = sr.role_id\n" +
                "INNER JOIN `sys_user` AS su\n" +
                "ON su.user_id = sur.user_id\n" +
                "WHERE\tsu.user_id = ?\n" +
                "AND\tsu.status = ?\n" +
                "AND sr.status = ?\n" +
                "AND sp.rule = ?",
                (rs, rowNum) -> {
                    String uri = rs.getString("uri");
                    String method = rs.getString("method");
                    String dataOperationRange = rs.getString("data_operation_range");
                    // 实例化权限项
                    PermissionEntry entry = new PermissionEntry();
                    entry.setUris(Arrays.asList(StringUtils.split(uri, ",")));
                    entry.setMethod(EntityEnum.getEnum(SysPermissionMethodEnum.class, method));
                    entry.setDataOperationRange(EntityEnum.getEnum(SysRoleDataOperationRangeEnum.class, dataOperationRange));
                    return entry;
                }, userId, SysUserStatusEnum.ENABLED.getValue(), SysRoleStatusEnum.ENABLED.getValue(), 1);
    }

    /**
     * 是否可操作所有的数据
     * @return bool
     */
    public static boolean canOperateAllData() {
        if (Objects.equals(AuthUtils.getLoggedUserId(), SecurityConstants.SUPER_ADMIN_USER_ID)) {
            return true;
        }
        return Objects.equals(AuthUtils.matchedPermissionEntryHolderStrategy
                .getPermissionEntry()
                .getDataOperationRange(), SysRoleDataOperationRangeEnum.ALL);
    }

}
